// Modified from the version on github. No error return codes, just die if
// a value isn't set right. This should vastly simmplify config.go
package ini

import (
	"bufio"
	"fmt"
    "log"
	"os"
	"regexp"
	"strconv"
	"strings"
	"unicode"
)

type Dict map[string]map[string]string

type Error string

var (
	regDoubleQuote = regexp.MustCompile("^([^= \t]+)[ \t]*=[ \t]*\"([^\"]*)\"$")
	regSingleQuote = regexp.MustCompile("^([^= \t]+)[ \t]*=[ \t]*'([^']*)'$")
	regNoQuote     = regexp.MustCompile("^([^= \t]+)[ \t]*=[ \t]*([^#;]+)")
	regNoValue     = regexp.MustCompile("^([^= \t]+)[ \t]*=[ \t]*([#;].*)?")
)

func Load(filename string) (dict Dict, err error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	dict = make(map[string]map[string]string)
	reader := bufio.NewReader(file)
	lineno := 0
	section := ""
	dict[section] = make(map[string]string)

	for err == nil {
		l, _, err := reader.ReadLine()
		if err != nil {
			break
		}
		lineno++
		if len(l) == 0 {
			continue
		}

		line := strings.TrimFunc(string(l), unicode.IsSpace)
		if len(line) == 0 {
			continue
		}

		for line[len(line)-1] == '\\' {
			line = line[:len(line)-1]
			l, _, err := reader.ReadLine()
			if err != nil {
				return nil, err
			}
			line += strings.TrimFunc(string(l), unicode.IsSpace)
		}

		section, err = dict.parseLine(section, line)
		if err != nil {
			return nil, newError(
				err.Error() + fmt.Sprintf("'%s:%d'.", filename, lineno))
		}
	}
	return
}

func (e Error) Error() string {
	return string(e)
}
func (dict Dict) parseLine(section, line string) (string, error) {
	// commets
	if line[0] == '#' || line[0] == ';' {
		return section, nil
	}

	// section name
	if line[0] == '[' && line[len(line)-1] == ']' {
		section := strings.TrimFunc(line[1:len(line)-1], unicode.IsSpace)
		section = strings.ToLower(section)
		dict[section] = make(map[string]string)
		return section, nil
	}

	// key = value
	if m := regDoubleQuote.FindAllStringSubmatch(line, 1); m != nil {
		dict.add(section, m[0][1], m[0][2])
		return section, nil
	} else if m = regSingleQuote.FindAllStringSubmatch(line, 1); m != nil {
		dict.add(section, m[0][1], m[0][2])
		return section, nil
	} else if m = regNoQuote.FindAllStringSubmatch(line, 1); m != nil {
		dict.add(section, m[0][1], strings.TrimFunc(m[0][2], unicode.IsSpace))
		return section, nil
	} else if m = regNoValue.FindAllStringSubmatch(line, 1); m != nil {
		dict.add(section, m[0][1], "")
		return section, nil
	}

	return section, newError("iniparser: syntax error at ")
}

func (dict Dict) add(section, key, value string) {
	key = strings.ToLower(key)
	dict[section][key] = value
}

func (dict Dict) GetBool(section, key string, fallback map[string]string) bool {
	sec, ok := dict[section]
    var boolVal bool
	if !ok {
        log.Fatal("Section "+section+"not found in config file.")
	}
	value, ok := sec[key]
	if !ok {
        if v, ok := fallback[key]; ok {
            b, _ := strconv.ParseBool(v)
            boolVal = b
        } else if section == "_default_" {
            boolVal = false
        } else {
            log.Fatal("Config Value "+key+" not set in config file " +
                      "for section "+section+"Refer to instructions " +
                      "in sample config file for more details.")
        }
	} else {
        v := value[0]
        if v == 'y' || v == 'Y' || v == '1' || v == 't' || v == 'T' {
            log.Printf("[%s] %s: true", section, key)
            boolVal = true
        } else  if v == 'n' || v == 'N' || v == '0' || v == 'f' || v == 'F' {
            log.Printf("[%s] %s: false", section, key)
            boolVal = false
        } else {
            log.Fatal("Section: "+section+" Option: "+key+" invalid setting.")
        }
    }
    return boolVal
}

func (dict Dict) GetString(section, key string,
                           fallback map[string]string) string {
    var stringVal string
	sec, ok := dict[section]
	if !ok {
        log.Fatal("Section "+section+"not found in config file.")
	}

	value, ok := sec[key]
    if ok {
        stringVal = value
    } else {
        if v, ok := fallback[key]; ok {
            stringVal = v
        } else if section == "_default_" {
            stringVal = ""
        } else {
            log.Fatal("Config Value "+key+" not set in config file " +
                      "for section "+section+"Refer to instructions " +
                      "in sample config file for more details.")
        }
	}
    log.Printf("[%s] %s: %s", section, key, stringVal)
	return stringVal
}

func (dict Dict) GetInt(section, key string,
                                 fallback map[string]string) int {
	sec, ok := dict[section]
    var intVal int
	if !ok {
        log.Fatal("Section "+section+"not found in config file.")
	}

	value, ok := sec[key]
    if ok {
        intVal, _ = strconv.Atoi(value)
    } else {
        if v, ok := fallback[key]; ok {
            intVal, _ = strconv.Atoi(v)
        } else if section == "_default_" {
            intVal = 0
        } else {
            log.Fatal("Config Value "+key+" not set in config file " +
                      "for section "+section+". Refer to instructions " +
                      "in sample config file for more details.")
        }
	}

    log.Printf("[%s] %s: %d", section, key, intVal)
	return intVal
}

func (dict Dict) GetDouble(section, key string,
                                    fallback map[string]string) float64 {
	sec, ok := dict[section]
    var doubleVal float64
	if !ok {
        log.Fatal("Section "+section+"not found in config file.")
	}
	value, ok := sec[key]
    if ok {
        doubleVal, _ = strconv.ParseFloat(value, 64)
    } else {
        if v, ok := fallback[key]; ok {
            f, _ := strconv.ParseFloat(v, 64)
            doubleVal = f
        } else if section == "_default_" {
            doubleVal = 0.0
        } else {
            log.Fatal("Config Value "+key+" not set in config file " +
                      "for section "+section+". Refer to instructions " +
                      "in sample config file for more details.")
        }
	}
    log.Printf("[%s] %s: %f", section, key, doubleVal)
	return doubleVal
}

func (dict Dict) GetSections() []string {
	size := len(dict)
	sections := make([]string, size)
	i := 0
	for section, _ := range dict {
		sections[i] = section
		i++
	}
	return sections
}

func newError(message string) (e error) {
	return Error(message)
}
